package com.diannuo.springboot.utils;

import javax.sql.DataSource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MapperGenerateUtil {

    public static void main(String[] args) {
        buildControllerAndService("com.diannuo.springboot","hello");
    }
    // 换行符
    private static String newLine = "\n";

    /**
     * 数据库表映射工具
     * @param dataSource
     * @param oneTableName 表名
     * @param basePath System.getProperty("user.dir");
     * @param modelPackage 实体类包路径
     * @param daoPackage dao接口包路径
     */
    public static void codegenForOneTable(DataSource dataSource, String oneTableName, String basePath, String modelPackage, String daoPackage) {
        String sourcePath = basePath + "src/main/java/";
        String sqlMapperPath = basePath + "src/main/resources/mapper/";
        String fileCharset = "utf-8";

        String modelFilePath;
        String daoFilePath;
        String sqlMapperFilePath;

        if (!sourcePath.endsWith(File.separator)) {
            sourcePath = sourcePath + File.separator;
        }
        if (!sqlMapperPath.endsWith(File.separator)) {
            sqlMapperPath = sqlMapperPath + File.separator;
        }
        modelFilePath = sourcePath + modelPackage.replace(".", File.separator) + File.separator
                        + getModelNameByTableName(oneTableName) + ".java";
        daoFilePath = sourcePath + daoPackage.replace(".", File.separator) + File.separator
                        + getDaoNameByTableName(oneTableName) + ".java";
        sqlMapperFilePath = sqlMapperPath + getSqlMapperFileNameByTableName(oneTableName);

        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            TableInfo tableInfo = getTableInfo(conn, oneTableName);
            if (tableInfo.getPrimaryKeys().size() == 0) {
                System.err.println("[ERROR] " + oneTableName + " 没有主键，无法生成。");
                return;
            }
            {
                File modelFile = new File(modelFilePath);
                if (modelFile.exists()) {
                    System.err.println("[WARN] 实体类 " + getModelNameByTableName(oneTableName) + " 已存在，将会覆盖。");
                }
                String modelSource = buildModel(tableInfo, modelPackage);
                writeText(modelFile, modelSource, fileCharset);
            }
            {
                File sqlMapperFile = new File(sqlMapperFilePath);
                if (!sqlMapperFile.exists()) {
                    // 自定义SQL文件不存在,才创建
                    String sqlMapperSource = buildSqlMapper(tableInfo, daoPackage);
                    writeText(sqlMapperFile, sqlMapperSource, fileCharset);
                }
            }
            {
                File daoFile = new File(daoFilePath);
                if (!daoFile.exists()) {
                    // 不存在才创建
                    String daoSource = buildDaoInterface(tableInfo, daoPackage, modelPackage);
                    writeText(daoFile, daoSource, fileCharset);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static String getAuthorInfo() {
        return "/**" + newLine +
                " * Created on " + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + newLine +
                " */" + newLine;
    }

    public static void buildControllerAndService(String packageName,String name){
        String sourcePath = System.getProperty("user.dir") + "/src/main/java/";
        String fileCharset = "utf-8";

        String controllerName = initCap(name)+"Controller";
        String controllerPackage = packageName +".controller";
        String controllerFilePath;
        String serviceName = initCap(name)+"Service";
        String servicePackage = packageName +".service";
        String serviceFilePath;

        controllerFilePath = sourcePath + controllerPackage.replace(".", File.separator) + File.separator
                + controllerName + ".java";
        serviceFilePath = sourcePath + servicePackage.replace(".", File.separator) + File.separator
                + serviceName + ".java";
        {
            File controllerFile = new File(controllerFilePath);
            if (!controllerFile.exists()) {
                // 不存在才创建
                String controllerSource = buildController(controllerName, controllerPackage, serviceName,servicePackage);
                writeText(controllerFile, controllerSource, fileCharset);
            }
        }
        {
            File serviceFile = new File(serviceFilePath);
            if (!serviceFile.exists()) {
                // 不存在才创建
                String serviceSource = buildService(serviceName,servicePackage);
                writeText(serviceFile, serviceSource, fileCharset);
            }
        }
    }

    private static String buildController(String controllerName, String controllerPackage, String serviceName, String servicePackage) {
        String downController = downCap(controllerName);
        String downService = downCap(serviceName);
        StringBuilder buf = new StringBuilder(1024);
        buf.append("package ").append(controllerPackage).append(";").append(newLine);
        buf.append(newLine);
        buf.append("import ").append(servicePackage).append(".").append(serviceName).append(";").append(newLine);
        buf.append("import org.springframework.web.bind.annotation.*;").append(newLine);
        buf.append(newLine);
        buf.append("import javax.annotation.Resource;").append(newLine);
        buf.append(newLine);
        buf.append("/**").append(newLine);
        buf.append(" * ").append(controllerName).append(newLine);
        buf.append(" */").append(newLine);
        buf.append("@RestController").append(newLine);
        buf.append("@RequestMapping(\"").append(downController).append("\")").append(newLine);
        buf.append("public class ").append(controllerName).append(" {").append(newLine);
        buf.append(newLine);
        buf.append("    @Resource").append(newLine);
        buf.append("    private ").append(serviceName).append(" ").append(downService).append(";").append(newLine);
        buf.append(newLine);
        buf.append("    @PostMapping(\"list\")").append(newLine);
        buf.append("    public String list(){").append(newLine);
        buf.append("        return null;").append(newLine);
        buf.append("    }").append(newLine);
        buf.append(newLine);
        buf.append("    @PostMapping(\"add\")").append(newLine);
        buf.append("    public String add(){").append(newLine);
        buf.append("        return null;").append(newLine);
        buf.append("    }").append(newLine);
        buf.append(newLine);
        buf.append("    @PostMapping(\"edit\")").append(newLine);
        buf.append("    public String edit(){").append(newLine);
        buf.append("        return null;").append(newLine);
        buf.append("    }").append(newLine);
        buf.append(newLine);
        buf.append("    @PostMapping(\"delete\")").append(newLine);
        buf.append("    public String delete(){").append(newLine);
        buf.append("        return null;").append(newLine);
        buf.append("    }").append(newLine);
        buf.append(newLine);
        buf.append("}");
        return buf.toString();
    }

    private static String buildService(String serviceName, String servicePackage) {
        StringBuilder buf = new StringBuilder(256);
        buf.append("package ").append(servicePackage).append(";").append(newLine);
        buf.append(newLine);
        buf.append("import org.springframework.stereotype.Service;").append(newLine);
        buf.append(newLine);
        buf.append("import javax.annotation.Resource;").append(newLine);
        buf.append(newLine);
        buf.append("@Service").append(newLine);
        buf.append("public class ").append(serviceName).append(" {").append(newLine);
        buf.append(newLine);
        buf.append("}");
        return buf.toString();
    }

    private static String buildModel(TableInfo tableInfo, String modalPackage) {
        StringBuilder buf = new StringBuilder(2048);
        String modalName = getModelNameByTableName(tableInfo.getTableName());
        buf.append("package ").append(modalPackage).append(";").append(newLine);
        buf.append(newLine);
        buf.append("import lombok.Getter;").append(newLine);
        buf.append("import lombok.Setter;").append(newLine);
        buf.append("import tk.mybatis.mapper.annotation.KeySql;").append(newLine);
        buf.append(newLine);
        buf.append("import javax.persistence.Id;").append(newLine);
        if (tableInfo.isImportTime()) {
            buf.append("import java.time.*;").append(newLine);
        }
        if (tableInfo.isImportMath()) {
            buf.append("import java.math.*;").append(newLine);
        }
        buf.append(newLine);
        buf.append(getAuthorInfo());
        buf.append("@Getter").append(newLine);
        buf.append("@Setter").append(newLine);
        buf.append("public class ").append(modalName).append(" implements java.io.Serializable {").append(newLine);
        buf.append(newLine);

        Map<String, String> columnCommentMap = tableInfo.getColumnCommentMap();

        //主键字段
        for (String primaryKey : tableInfo.getPrimaryKeys()) {
            String propertyName = underlineToCamel(primaryKey);
            String javaType = getJavaTypeByJdbcType(tableInfo.getColumnTypes().get(primaryKey));

            String comment = columnCommentMap.get(primaryKey);
            if (comment != null) {
                buf.append("    /**").append(newLine);
                buf.append("     * ").append(comment).append(newLine);
                buf.append("     */").append(newLine);
            }
            buf.append("    ").append("@Id").append(newLine);
            if (tableInfo.isPrimaryKeyAutoIncrement()){
                buf.append("    ").append("@KeySql(useGeneratedKeys = true)").append(newLine);
            }
            buf.append("    ").append("private ").append(javaType).append(" ").append(propertyName).append(";").append(newLine);
        }
        //其他字段
        for (String column : tableInfo.getColumns()) {
            if (tableInfo.getPrimaryKeys().contains(column)) {
                continue;
            }
            String propertyName = underlineToCamel(column);
            String javaType = getJavaTypeByJdbcType(tableInfo.getColumnTypes().get(column));

            String comment = columnCommentMap.get(column);
            if (comment != null) {
                buf.append("    /**").append(newLine);
                buf.append("     * ").append(comment).append(newLine);
                buf.append("     */");
            }
            buf.append(newLine);
            buf.append("    ").append("private ").append(javaType).append(" ").append(propertyName).append(";").append(newLine);
        }
        buf.append(newLine);
        buf.append("}");
        return buf.toString();
    }

    private static String buildDaoInterface(TableInfo tableInfo, String daoPackage, String modalPackage) {
        StringBuilder buf = new StringBuilder(1024);
        String tableName = tableInfo.getTableName();
        String daoName = getDaoNameByTableName(tableName);
        String modalName = getModelNameByTableName(tableInfo.getTableName());
        String modalNameWithPackage = modalPackage + "." + modalName;
        buf.append("package ").append(daoPackage).append(";").append(newLine);
        buf.append(newLine);
        buf.append("import ").append("com.gitee.target123.develop.mysql.config.CommonMapper;").append(newLine);
        buf.append("import ").append(modalNameWithPackage).append(";").append(newLine);
        buf.append(newLine);
        buf.append(getAuthorInfo());
        buf.append("public interface ").append(daoName).append(" extends CommonMapper<").append(modalName).append(">").append(" {").append(newLine);
        buf.append(newLine);
        buf.append("}");
        return buf.toString();
    }

    private static String buildSqlMapper(TableInfo tableInfo, String daoPackage) {
        StringBuilder buf = new StringBuilder(1024);
        String daoName = daoPackage + "." + getDaoNameByTableName(tableInfo.getTableName());
        buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>").append(newLine);
        buf.append("<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\" >").append(newLine);
        buf.append("<mapper namespace=\"").append(daoName).append("\">").append(newLine).append(newLine);
        buf.append("</mapper>");
        return buf.toString();
    }

    private static String getJavaTypeByJdbcType(String jdbcType) {
        if (jdbcType.equalsIgnoreCase("int") || jdbcType.equalsIgnoreCase("integer") || jdbcType
                .equalsIgnoreCase("tinyint")) {
            return "Integer";
        } else if (jdbcType.equalsIgnoreCase("bigint")) {
            return "Long";
        } else if (jdbcType.equalsIgnoreCase("varchar") || jdbcType.equalsIgnoreCase("text") || jdbcType.equalsIgnoreCase("longtext")) {
            return "String";
        } else if (jdbcType.equalsIgnoreCase("double")) {
            return "Double";
        } else if (jdbcType.equalsIgnoreCase("float")) {
            return "Float";
        } else if (jdbcType.equalsIgnoreCase("decimal")) {
            return "BigDecimal";
        } else if (jdbcType.equalsIgnoreCase("datetime") || jdbcType
                .equalsIgnoreCase("timestamp")) {
            return "LocalDateTime";
        } else if (jdbcType.equalsIgnoreCase("date")) {
            return "LocalDate";
        } else if (jdbcType.equalsIgnoreCase("time")) {
            return "LocalTime";
        }
        throw new RuntimeException("not supported JDBC Type : \"" + jdbcType + "\"!");
    }

    private static TableInfo getTableInfo(Connection conn, String tableName)
            throws Exception {
        //查要生成实体类的表
        String schema = conn.getCatalog();
        String sql = "SELECT * FROM " + schema + "." + tableName;
        PreparedStatement pStemt;
        TableInfo tableInfo;
        try {
            pStemt = conn.prepareStatement(sql);
            ResultSetMetaData rsmd = pStemt.getMetaData();
            int size = rsmd.getColumnCount();//统计列
            List<String> columns = new ArrayList<>(size);
            Map<String, String> columnTypes = new HashMap<>(size);
            Map<String, String> columnCommentMap = new HashMap<>(size);
            // 是否需要导入包java.math.*
            boolean importMath = false;
            // 是否需要导入包java.time.*
            boolean importTime = false;
            {
                String commentSql =
                        "SELECT column_name,column_comment FROM information_schema.COLUMNS WHERE table_name='"
                                + tableName + "' and table_schema = '" + schema + "'";
                PreparedStatement pStemt2 = conn.prepareStatement(commentSql);
                ResultSet rs = pStemt2.executeQuery();
                while (rs.next()) {
                    String column_name = rs.getString("column_name").toLowerCase();
                    String column_comment = rs.getString("column_comment");
                    if (column_comment != null && column_comment.length() > 0) {
                        columnCommentMap.put(column_name, column_comment);
                    }
                }
            }

            for (int i = 0; i < size; i++) {
                String columnName = rsmd.getColumnName(i + 1).toLowerCase();
                columns.add(columnName);
                String colType = rsmd.getColumnTypeName(i + 1);
                colType = colType.replace("UNSIGNED", "").replace("unsigned", "").trim().toUpperCase();
                columnTypes.put(columnName, colType);
                if (colType.equalsIgnoreCase("datetime") || colType.equalsIgnoreCase("timestamp")
                    ||colType.equalsIgnoreCase("date")||colType.equalsIgnoreCase("time")) {
                    importTime = true;
                } else if (colType.equalsIgnoreCase("decimal")) {
                    importMath = true;
                }
            }
            List<String> primaryKeys = new ArrayList<>(2);
            {
                String selectPrimaryKeysSql =
                        "SELECT column_name FROM information_schema.KEY_COLUMN_USAGE WHERE constraint_name='PRIMARY' AND table_name='"
                                + tableName + "' and table_schema = '" + schema + "'";
                PreparedStatement pStemt2 = conn.prepareStatement(selectPrimaryKeysSql);
                ResultSet rs = pStemt2.executeQuery();
                while (rs.next()) {
                    String primaryKey = rs.getString("column_name");
                    primaryKeys.add(primaryKey);
                }
            }

            // 否则主键一定不是自增长
            boolean primaryKeyAutoIncrement = false;
            if (primaryKeys.size() == 1) {
                // 有主键，且主键只有一个，才判断主键是否是自增长
                String selectPrimaryKeyAutoIncrementSql =
                        "SELECT auto_increment FROM information_schema.TABLES WHERE table_name='"
                                + tableName + "' and table_schema = '" + schema + "'";
                PreparedStatement pStemt3 = conn.prepareStatement(selectPrimaryKeyAutoIncrementSql);
                ResultSet rs = pStemt3.executeQuery();
                if (rs.next()) {
                    Object autoIncrementObj = rs.getObject("auto_increment");
                    if (autoIncrementObj != null) {
                        // 自增长主键
                        primaryKeyAutoIncrement = true;
                    }
                }
            }

            tableInfo = new TableInfo();
            tableInfo.setTableName(tableName);
            tableInfo.setColumns(columns);
            tableInfo.setColumnTypes(columnTypes);
            tableInfo.setImportTime(importTime);
            tableInfo.setImportMath(importMath);
            tableInfo.setPrimaryKeyAutoIncrement(primaryKeyAutoIncrement);
            tableInfo.setPrimaryKeys(primaryKeys);
            tableInfo.setColumnCommentMap(columnCommentMap);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return tableInfo;
    }

    private static String getSqlMapperFileNameByTableName(String tableName) {
        return tableName + "-mapper.xml";
    }

    private static String getModelNameByTableName(String tableName) {
        return initCapAndUnderlineToCamel(tableName);
    }

    private static String getDaoNameByTableName(String tableName) {
        return getModelNameByTableName(tableName) + "Mapper";
    }

    private static void writeText(File targetFile, String content, String charSet) {
        OutputStreamWriter out = null;
        if (!targetFile.getParentFile().exists()) {
            targetFile.getParentFile().mkdirs();
        }
        try {
            out = new OutputStreamWriter(new FileOutputStream(targetFile), charSet);
            out.write(content.toCharArray());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static String initCap(String src) {
        if (src == null) {
            return null;
        }
        if (src.length() > 1) {
            return src.substring(0, 1).toUpperCase() + src.substring(1);
        } else {
            return src.toUpperCase();
        }
    }
    private static String downCap(String src) {
        if (src.length() > 1) {
            return src.substring(0, 1).toLowerCase() + src.substring(1);
        } else {
            return src.toLowerCase();
        }
    }

    /**
     * 驼峰处理shop_name -> shopName
     */
    private static String underlineToCamel(String param) {
        if (param == null || param.equals("")) {
            return "";
        }
        Pattern p = Pattern.compile("_");
        StringBuilder builder = new StringBuilder(param);
        Matcher mc = p.matcher(param);
        int i = 0;
        char found;
        while (mc.find()) {
            builder.deleteCharAt(mc.start() - i);
            found = builder.charAt(mc.start() - i);
            builder.replace(mc.start() - i, mc.start() - i + 1, Character.toString(found).toUpperCase());
            i++;
        }
        return builder.toString();
    }

    /**
     * 驼峰处理ly_share_user -> LyShareUser
     */
    private static String initCapAndUnderlineToCamel(String param) {
        String rs = underlineToCamel(param);
        return initCap(rs);
    }

    private static class TableInfo {

        private String tableName;
        private List<String> primaryKeys;
        private boolean primaryKeyAutoIncrement;
        private List<String> columns;
        private Map<String, String> columnTypes;
        private Map<String, String> columnCommentMap;
        // 是否需要导入包java.math.*
        private boolean importMath;
        // 是否需要导入包java.time.*
        private boolean importTime;

        String getTableName() {
            return tableName;
        }

        void setTableName(String tableName) {
            this.tableName = tableName;
        }

        List<String> getPrimaryKeys() {
            return primaryKeys;
        }

        void setPrimaryKeys(List<String> primaryKeys) {
            this.primaryKeys = primaryKeys;
        }

        boolean isPrimaryKeyAutoIncrement() {
            return primaryKeyAutoIncrement;
        }

        void setPrimaryKeyAutoIncrement(boolean primaryKeyAutoIncrement) {
            this.primaryKeyAutoIncrement = primaryKeyAutoIncrement;
        }

        List<String> getColumns() {
            return columns;
        }

        void setColumns(List<String> columns) {
            this.columns = columns;
        }

        Map<String, String> getColumnTypes() {
            return columnTypes;
        }

        void setColumnTypes(Map<String, String> columnTypes) {
            this.columnTypes = columnTypes;
        }

        Map<String, String> getColumnCommentMap() {
            return columnCommentMap;
        }

        void setColumnCommentMap(Map<String, String> columnCommentMap) {
            this.columnCommentMap = columnCommentMap;
        }

        boolean isImportMath() {
            return importMath;
        }

        void setImportMath(boolean importMath) {
            this.importMath = importMath;
        }

        boolean isImportTime() {
            return importTime;
        }

        void setImportTime(boolean importTime) {
            this.importTime = importTime;
        }
    }
}
